package com.hailiang.study.poi.word.util;

import org.apache.poi.xwpf.usermodel.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 参考：https://blog.csdn.net/u012775558/article/details/79678701?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163419051916780357277333%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163419051916780357277333&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~baidu_landing_v2~default-1-79678701.pc_v2_rank_blog_default&utm_term=poi+doc%E6%A8%A1%E6%9D%BF&spm=1018.2226.3001.4450
 * @author hailiang.jiang
 * @date 2021年10月14 13:55
 */
public class WordTemplate {
    private XWPFDocument document;

    public XWPFDocument getDocument() {
        return document;
    }

    public void setDocument(XWPFDocument document) {
        this.document = document;
    }

    /**
     * 初始化模板內容
     * @author hailiang.jiang
     * @date 2021/10/14 13:57
     * @param inputStream 模板的读取流(docx文件)
     */
    public WordTemplate(InputStream inputStream) throws IOException {
        this.document = new XWPFDocument(inputStream);
    }


    /**
     * 将处理后的内容写入到输出流中
     * @author hailiang.jiang
     * @date 2021/10/14 13:58
     * @param outputStream 模板的输出流
     */
    public void write(OutputStream outputStream) throws IOException {
        document.write(outputStream);
    }

    public void replaceDocument(Map<String, Object> dataMap) {
        if (!dataMap.containsKey("parametersMap")) {
            System.out.println("数据源错误--数据源(parametersMap)缺失");
            return;
        }
        Map<String, Object> parametersMap = (Map<String, Object>) dataMap.get("parametersMap");

        //所有对象（段落+表格）
        List<IBodyElement> bodyElements = document.getBodyElements();
        // 标记模板文件（段落+表格）总个数
        int templateBodySize = bodyElements.size();

        int curT = 0;// 当前操作表格对象的索引
        int curP = 0;// 当前操作段落对象的索引

        for (int i = 0; i < templateBodySize; i++) {
            IBodyElement body = bodyElements.get(i);
            if (BodyElementType.TABLE == body.getElementType()) {
                // 处理表格
                XWPFTable table = body.getBody().getTableArray(curT);
                if (Objects.nonNull(table)) {
                    //获取到模板表格第一行，用来判断表格类型
                    List<XWPFTableCell> tableCells = table.getRows().get(0).getTableCells();

                    // 表格中的所有文本
                    String tableText = table.getText();

                    String prefixForeachTagBegin = "##{foreach";
                    if (tableText.indexOf(prefixForeachTagBegin) > -1) {
                        // 查找到##{foreach标签，该表格需要处理循环
                        if (tableCells.size() != 2
                                || tableCells.get(0).getText().indexOf(prefixForeachTagBegin) < 0
                                || tableCells.get(0).getText().trim().length() == 0
                        ) {
                            System.out.println("文档中第"
                                            + (curT + 1)
                                            + "个表格模板错误,模板表格第一行需要设置2个单元格，"
                                            + "第一个单元格存储表格类型(##{foreachTable}## 或者 ##{foreachTableRow}##)，第二个单元格定义数据源。");
                            return;
                        }

                        String tableType = tableCells.get(0).getText();
                        String dataSource = tableCells.get(1).getText();
                        System.out.println("读取到数据源：" + dataSource);
                        if (!dataMap.containsKey(dataSource)) {
                            System.out.println("文档中第" + (curT + 1) + "个表格模板数据源[" + dataSource + "]缺失");
                            return;
                        }

                        List<Map<String, Object>> tableDataList = (List<Map<String, Object>>) dataMap.get(dataSource);
                        String foreachTableTag = "##{foreachTable}##";
                        String foreachTableRowTag = "##{foreachTableRow}##";
                        if (foreachTableTag.equals(tableType)) {
                            // 循环生成表格
                            addTableInDocFooter(table, tableDataList, parametersMap, 1);
                        } else if (foreachTableRowTag.equals(tableType)) {
                            //循环生成表格内部的行
                            addTableInDocFooter(table, tableDataList, parametersMap, 2);
                        }
                    }
                }
            } else if (BodyElementType.PARAGRAPH == body.getElementType()) {
                //获取到段落
                XWPFParagraph ph = body.getBody().getParagraphArray(curP);
                if (Objects.nonNull(ph)) {
                    addParagraphInDocFooter(ph, null, parametersMap, 0);
                    curP ++;
                }
            }
        }

        // 处理完毕模板，删除文本中的模板内容
        for (int a = 0; a < templateBodySize; a++) {
            document.removeBodyElement(0);
        }
    }

    private void addParagraphInDocFooter(XWPFParagraph templateParagraph, List<Map<String, String>> list, Map<String, Object> parametersMap, int flag) {
        if (flag == 0) {
            XWPFParagraph createParagraph = document.createParagraph();
            // 设置段落样式
            createParagraph.getCTP().setPPr(templateParagraph.getCTP().getPPr());
            // 移除原始内容
            for (int pos = 0; pos < createParagraph.getRuns().size(); pos++) {
                createParagraph.removeRun(pos);
            }
            // 添加Run标签
            for (XWPFRun s : templateParagraph.getRuns()) {
                XWPFRun targetRun = createParagraph.createRun();
                copyRun(targetRun, s);
            }

            replaceParagraph(createParagraph, parametersMap);
        } else if (flag == 1) {
            //暂无实现
        }

    }

    /**
     * 根据 模板表格 和 数据list 在word文档末尾生成表格
     * @author hailiang.jiang
     * @date 2021/10/14 15:00
     * @param templateTable 模板表格
     * @param tableDataList 循环数据集
     * @param parametersMap 不循环数据集
     * @param flag (0为静态表格，1为表格整体循环，2为表格内部行循环，3为表格不循环仅简单替换标签即可)
     */
    private void addTableInDocFooter(XWPFTable templateTable, List<Map<String, Object>> tableDataList, Map<String, Object> parametersMap, int flag) {
        if (flag == 1) {
            for (Map<String, Object> map : tableDataList) {
                //获取模板表格所有行
                List<XWPFTableRow> templateTableRows = templateTable.getRows();
                //创建新表格,默认一行一列
                XWPFTable newCreateTable = document.createTable();
                for (int i = 1; i < templateTableRows.size(); i++) {//忽略第一行模板标签
                    XWPFTableRow newCreateRow = newCreateTable.createRow();
                    //复制模板行和样式到新行
                    copyTableRow(newCreateRow, templateTableRows.get(i));
                    //替换标签
                    replaceTable(newCreateTable, map);
                }
            }
        } else if (flag == 2) {// 表格表格内部行循环
            XWPFTable newCreateTable = document.createTable();// 创建新表格,默认一行一列
            List<XWPFTableRow> TempTableRows = templateTable.getRows();// 获取模板表格所有行
            int tagRowsIndex = 0;// 标签行indexs
            for (int i = 0, size = TempTableRows.size(); i < size; i++) {
                String rowText = TempTableRows.get(i).getCell(0).getText();// 获取到表格行的第一个单元格
                if (rowText.indexOf("##{foreachRows}##") > -1) {
                    tagRowsIndex = i;
                    break;
                }
            }

            /* 复制模板行和标签行之前的行 */
            for (int i = 1; i < tagRowsIndex; i++) {
                XWPFTableRow newCreateRow = newCreateTable.createRow();
                copyTableRow(newCreateRow, TempTableRows.get(i));// 复制行
                replaceTableRow(newCreateRow, parametersMap);// 处理不循环标签的替换
            }

            /* 循环生成模板行 */
            XWPFTableRow tempRow = TempTableRows.get(tagRowsIndex + 1);// 获取到模板行
            for (int i = 0; i < tableDataList.size(); i++) {
                XWPFTableRow newCreateRow = newCreateTable.createRow();
                copyTableRow(newCreateRow, tempRow);// 复制模板行
                replaceTableRow(newCreateRow, tableDataList.get(i));// 处理标签替换
            }

            /* 复制模板行和标签行之后的行 */
            for (int i = tagRowsIndex + 2; i < TempTableRows.size(); i++) {
                XWPFTableRow newCreateRow = newCreateTable.createRow();
                copyTableRow(newCreateRow, TempTableRows.get(i));// 复制行
                replaceTableRow(newCreateRow, parametersMap);// 处理不循环标签的替换
            }
            newCreateTable.removeRow(0);// 移除多出来的第一行
            document.createParagraph();// 添加回车换行
        }
    }

    /**
     * 根据参数parametersMap对表格的一行进行标签的替换
     * @author hailiang.jiang
     * @date 2021/10/14 17:21
     * @param newCreateRow  表格行
     * @param parametersMap 参数map
     */
    private void replaceTableRow(XWPFTableRow newCreateRow, Map<String, Object> parametersMap) {
        List<XWPFTableCell> tableCells = newCreateRow.getTableCells();
        for (XWPFTableCell xWPFTableCell : tableCells) {
            List<XWPFParagraph> paragraphs = xWPFTableCell.getParagraphs();
            for (XWPFParagraph xwpfParagraph : paragraphs) {
                replaceParagraph(xwpfParagraph, parametersMap);
            }
        }
    }

    /**
     * 根据map替换表格中的{key}标签
     * @author hailiang.jiang
     * @date 2021/10/14 15:33
     * @param xwpfTable
     * @param parametersMap 
     */
    private void replaceTable(XWPFTable xwpfTable, Map<String, Object> parametersMap) {
        List<XWPFTableRow> rows = xwpfTable.getRows();
        for (XWPFTableRow xWPFTableRow : rows ) {
            List<XWPFTableCell> tableCells = xWPFTableRow.getTableCells();
            for (XWPFTableCell xWPFTableCell : tableCells ) {
                List<XWPFParagraph> paragraphsList = xWPFTableCell.getParagraphs();
                for (XWPFParagraph xWPFParagraph : paragraphsList) {
                    replaceParagraph(xWPFParagraph, parametersMap);
                }
            }
        }
    }

    /**
     * 根据map替换段落元素内的{**}标签
     * @author hailiang.jiang
     * @date 2021/10/14 15:34
     * @param xWPFParagraph
     * @param parametersMap
     */
    private void replaceParagraph(XWPFParagraph xWPFParagraph, Map<String, Object> parametersMap) {
        String regEx = "\\{.+?\\}";
        String xWPFParagraphText = xWPFParagraph.getText();
        Pattern pattern = Pattern.compile(regEx);
        Matcher matcher = pattern.matcher(xWPFParagraphText);//正则匹配字符串{****}

        List<XWPFRun> runs = xWPFParagraph.getRuns();
        if (matcher.find()) {
            //查找到有标签，才执行替换
            int beginRunIndex = xWPFParagraph.searchText("{", new PositionInParagraph()).getBeginRun();//标签开始run位置
            int endRunIndex = xWPFParagraph.searchText("}", new PositionInParagraph()).getEndRun();//标签结束run位置
            StringBuffer key = new StringBuffer();

            if (beginRunIndex == endRunIndex) {
                //{**}在一个run标签内
                XWPFRun beginRun = runs.get(beginRunIndex);
                String beginRunText = beginRun.text();

                int beginIndex = beginRunText.indexOf("{");
                int endIndex = beginRunText.indexOf("}");
                int length = beginRunText.length();


                if (beginIndex == 0 && endIndex == length - 1) {
                    // 该run标签只有{**}
                    XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
                    insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
                    // 设置文本
                    key.append(beginRunText.substring(1, endIndex));
                    insertNewRun.setText(getValueBykey(key.toString(), parametersMap));
                    xWPFParagraph.removeRun(beginRunIndex + 1);
                } else {
                    // 该run标签为**{**}** 或者 **{**} 或者{**}**，替换key后，还需要加上原始key前后的文本
                    XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
                    insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
                    // 设置文本
                    key.append(beginRunText.substring(beginRunText.indexOf("{")+1, beginRunText.indexOf("}")));
                    String textString=beginRunText.substring(0, beginIndex) + getValueBykey(key.toString(),parametersMap)
                            + beginRunText.substring(endIndex + 1);
                    insertNewRun.setText(textString);
                    xWPFParagraph.removeRun(beginRunIndex + 1);
                }
            } else {
                // {**}被分成多个run
                //先处理起始run标签,取得第一个{key}值
                XWPFRun beginRun = runs.get(beginRunIndex);
                String beginRunText = beginRun.text();
                int beginIndex = beginRunText.indexOf("{");
                if (beginRunText.length()>1  ) {
                    key.append(beginRunText.substring(beginIndex+1));
                }
                ArrayList<Integer> removeRunList = new ArrayList<>();//需要移除的run
                //处理中间的run
                for (int i = beginRunIndex + 1; i < endRunIndex; i++) {
                    XWPFRun run = runs.get(i);
                    String runText = run.text();
                    key.append(runText);
                    removeRunList.add(i);
                }

                // 获取endRun中的key值
                XWPFRun endRun = runs.get(endRunIndex);
                String endRunText = endRun.text();
                int endIndex = endRunText.indexOf("}");
                //run中**}或者**}**
                if (endRunText.length()>1 && endIndex!=0) {
                    key.append(endRunText.substring(0,endIndex));
                }

                //*******************************************************************
                //取得key值后替换标签

                //先处理开始标签
                if (beginRunText.length()==2 ) {
                    // run标签内文本{
                    XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
                    insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
                    // 设置文本
                    insertNewRun.setText(getValueBykey(key.toString(),parametersMap));
                    xWPFParagraph.removeRun(beginRunIndex + 1);//移除原始的run
                } else {
                    // 该run标签为**{**或者 {** ，替换key后，还需要加上原始key前的文本
                    XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
                    insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
                    // 设置文本
                    String textString=beginRunText.substring(0,beginRunText.indexOf("{"))+getValueBykey(key.toString(),parametersMap);
                    insertNewRun.setText(textString);
                    xWPFParagraph.removeRun(beginRunIndex + 1);//移除原始的run
                }

                //处理结束标签
                if (endRunText.length()==1 ) {
                    // run标签内文本只有}
                    XWPFRun insertNewRun = xWPFParagraph.insertNewRun(endRunIndex);
                    insertNewRun.getCTR().setRPr(endRun.getCTR().getRPr());
                    // 设置文本
                    insertNewRun.setText("");
                    xWPFParagraph.removeRun(endRunIndex + 1);//移除原始的run

                }else {
                    // 该run标签为**}**或者 }** 或者**}，替换key后，还需要加上原始key后的文本
                    XWPFRun insertNewRun = xWPFParagraph.insertNewRun(endRunIndex);
                    insertNewRun.getCTR().setRPr(endRun.getCTR().getRPr());
                    // 设置文本
                    String textString=endRunText.substring(endRunText.indexOf("}")+1);
                    insertNewRun.setText(textString);
                    xWPFParagraph.removeRun(endRunIndex + 1);//移除原始的run
                }

                //处理中间的run标签
                for (int i = 0; i < removeRunList.size(); i++) {
                    XWPFRun xWPFRun = runs.get(removeRunList.get(i));//原始run
                    XWPFRun insertNewRun = xWPFParagraph.insertNewRun(removeRunList.get(i));
                    insertNewRun.getCTR().setRPr(xWPFRun.getCTR().getRPr());
                    insertNewRun.setText("");
                    xWPFParagraph.removeRun(removeRunList.get(i) + 1);//移除原始的run
                }

            }
            // 处理${**}被分成多个run
            replaceParagraph( xWPFParagraph, parametersMap);
        }


    }

    /**
     * 复制表格行XWPFTableRow格式
     * @author hailiang.jiang
     * @date 2021/10/14 15:14
     * @param targetRow 待修改格式的XWPFTableRow
     * @param sourceRow 模板XWPFTableRow
     */
    private void copyTableRow(XWPFTableRow targetRow, XWPFTableRow sourceRow) {
        //模板行的列数
        int templateRowCellsSize = sourceRow.getTableCells().size();
        for (int i = 0; i < templateRowCellsSize - 1; i++) {
            //为新添加的行，增加与模板表格对应行相同个数的单元格
            targetRow.addNewTableCell();
        }
        //复制样式
        targetRow.getCtRow().setTrPr(sourceRow.getCtRow().getTrPr());
        //复制单元格
        for (int i = 0; i < targetRow.getTableCells().size(); i++) {
            copyTableCell(targetRow.getCell(i), sourceRow.getCell(i));
        }
    }

    /**
     * 复制单元格XWPFTableCell格式
     * @author hailiang.jiang
     * @date 2021/10/14 15:14
     * @param targetCell 新创建的的单元格
     * @param sourceCell 模板单元格
     */
    private void copyTableCell(XWPFTableCell targetCell, XWPFTableCell sourceCell) {
        //列属性
        targetCell.getCTTc().setTcPr(sourceCell.getCTTc().getTcPr());
        //删除模板所有文本段落
        for (int pos = 0; pos < targetCell.getParagraphs().size(); pos++) {
            targetCell.removeParagraph(pos);
        }
        //添加新的文本段落
        for (XWPFParagraph sp : sourceCell.getParagraphs()) {
            XWPFParagraph targetParagraph = targetCell.addParagraph();
            copyParagraph(targetParagraph, sp);
        }
    }

    /**
     * 复制文本段落XWPFParagraph格式
     * @author hailiang.jiang
     * @date 2021/10/14 15:20
     * @param targetParagraph 新创建的的段落
     * @param sourceParagraph 模板段落
     */
    private void copyParagraph(XWPFParagraph targetParagraph, XWPFParagraph sourceParagraph) {
        //设置段落样式
        targetParagraph.getCTP().setPPr(sourceParagraph.getCTP().getPPr());
        //添加Run标签
        for (int pos = 0; pos < targetParagraph.getRuns().size(); pos++) {
            targetParagraph.removeRun(pos);
        }
        for (XWPFRun sourceRun : sourceParagraph.getRuns()) {
            XWPFRun targetRun = targetParagraph.createRun();
            copyRun(targetRun, sourceRun);
        }
    }

    /**
     * 复制文本节点run
     * @author hailiang.jiang
     * @date 2021/10/14 15:25
     * @param targetRun 新创建的的文本节点
     * @param sourceRun 模板文本节点
     */
    private void copyRun(XWPFRun targetRun, XWPFRun sourceRun) {
        targetRun.getCTR().setRPr(sourceRun.getCTR().getRPr());
        //设置文本
        targetRun.setText(sourceRun.text());
    }

    private String getValueBykey(String key, Map<String, Object> map) {
        String returnValue="";
        if (key != null) {
            try {
                returnValue= map.get(key)!=null ? map.get(key).toString() : "";
            } catch (Exception e) {
                System.out.println("key:"+key+"***"+e);
                returnValue="";
            }

        }
        return returnValue;
    }
}
